<?php

namespace slinks\core;

use slinks\SlinksPaths;
use slinks\common\io\InterfaceCollectingClassVisitor;
use slinks\common\dynamic\DynamicPhpManager;
use slinks\common\dynamic\IDynamic;
use slinks\core\DependencyInjection\Extension\ExtensionClassVisitor;
use slinks\common\io\ClassReader;
use slinks\common\Finder\Finder;
use slinks\core\DependencyInjection\Dumper\PhpDumper;
use slinks\core\DependencyInjection\ContainerInterface;
use slinks\core\DependencyInjection\Loader\LoaderResolver;
use slinks\core\DependencyInjection\Loader\PhpFileLoader;
use slinks\core\DependencyInjection\Loader\IniFileLoader;
use slinks\core\DependencyInjection\Loader\YamlFileLoader;
use slinks\core\DependencyInjection\Loader\XmlFileLoader;
use slinks\core\DependencyInjection\Loader\DelegatingLoader;
use slinks\core\DependencyInjection\ParameterBag\ParameterBag;
use slinks\core\DependencyInjection\ContainerBuilder;
use slinks\core\dependencyInjectionExt\ContainerUtil;

/**
 * Proxies a ContainerBuilder and provides automatic cache support.
 * @author Navid Mitchell
 *
 */
class SlinksContainer implements ContainerInterface, IDynamic {

    protected $container = null;
    private $applicationConfigDir;
    private $parameterBag;
    private $autoRegisterNamespace;
    private $applicationFileSuffix;
    private $className = null;
    
    /**
     * Creates a new Slinks container
     *
     * @param string $directory to the main configuration file
     * @param array $extensionDirs an array of directories to search for extension objects to be used by this container.
     * @param ParameterBag $parameterBag parameters to be used by this container if desired.
     */
    public function __construct($applicationConfigDir,
                                 $cacheDir, 
                                 ParameterBag $parameterBag, 
                                 $debug,
                                 $autoRegisterNamespace,
                                 $applicationFileSuffix) {

        $this->applicationConfigDir = $applicationConfigDir;
        $this->parameterBag = $parameterBag;
        $this->autoRegisterNamespace = $autoRegisterNamespace;
        $this->applicationFileSuffix = $applicationFileSuffix;

        DynamicPhpManager::getInstance()->setDebug($debug);
        DynamicPhpManager::getInstance()->setCacheDirectory($cacheDir);
        DynamicPhpManager::getInstance()->initialize($this);

        // if we did not already build this.
        // FIXME: the line below does not work for the actio test for some reason the container is invalid until dumped.
        //if($this->container == null){
        $class = $this->getClassName();
        $this->container = new $class();
        //}
    }

    /**
     * @return string that is a unique name for this IDynamic object
     */
    public function getClassName() {
        if ($this->className == null) {
            // use the real path so the hash is unique to the actual path.
            $info = new \SplFileInfo($this->applicationConfigDir);
            $this->className = 'SlinksContainer' . '_' . $this->getHash($info->getRealPath());
        }
        return $this->className;
    }

    /**
     * @return a string which contains the dynamically generated php.
     */
    public function getPhp() {
        // build the container
        $this->container = $this->buildContainer();

        $dumper = new PhpDumper($this->container);

        return $dumper->dump(array('class' => $this->getClassName(),'base_class'=>'slinks\\core\\dependencyInjectionExt\\ContainerExt'));
    }

    /**
     * Returns the resources that this dynamic object represents.
     * This is used to determine if the IDynamic object should be re-built.
     * @return array of ResourceInterface objects.
     */
    public function getResources() {
        return $this->container->getResources();
    }

    protected function buildContainer() {

        $container = new ContainerBuilder($this->parameterBag);

        //add slinks core components
        //add compilers
        $finder = new Finder();
        ContainerUtil::addCompilers($container, $finder->files()->in(SlinksPaths::coreCompilerDirs()));
        
        // Add extensions
        ContainerUtil::addExtensions($container, SlinksPaths::coreExtensionsDir());
  
        // Add client application
        ContainerUtil::addApplications($container, 
                                       $this->applicationConfigDir, 
                                       $this->autoRegisterNamespace, 
                                       $this->applicationFileSuffix, 
                                       1);
        $container->compile();

        return $container;
    }



    private function getHash($dirString) {
        return md5($dirString);
    }

    /**
     * Proxy calls to container. 
     *
     * @param string $method
     * @param array $args
     * @return mixed
     */
    public function __call($method, $args) {
        if (method_exists($this->container, $method)) {
            return call_user_func_array(array($this->container, $method), $args);
        } else {
            throw new \BadMethodCallException('Method ' . $method . ' not implemented for ContainerBuilder.');
        }
    }

    /**
     * Sets a service.
     *
     * @param string $id      The service identifier
     * @param object $service The service instance
     * @param string $scope   The scope of the service
     */
    public function set($id, $service, $scope = self::SCOPE_CONTAINER) {
        $this->container->set($id, $service);
    }

    /**
     * Gets a service.
     *
     * @param  string $id              The service identifier
     * @param  int    $invalidBehavior The behavior when the service does not exist
     *
     * @return object The associated service
     *
     * @throws \InvalidArgumentException if the service is not defined
     *
     * @see Reference
     */
    public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE) {
        return $this->container->get($id, $invalidBehavior);
    }

    /**
     * Returns true if the given service is defined.
     *
     * @param  string  $id      The service identifier
     *
     * @return Boolean true if the service is defined, false otherwise
     */
    public function has($id) {
        return $this->container->has($id);
    }

    /**
     * Enters the given scope
     *
     * @param string $name
     * @return void
     */
    function enterScope($name) {
        $this->container->enterScope($name);
    }

    /**
     * Leaves the current scope, and re-enters the parent scope
     *
     * @param string $name
     * @return void
     */
    function leaveScope($name) {
        $this->container->leaveScope($name);
    }

    /**
     * Adds a scope to the container
     *
     * @param string $name
     * @param string $parentScope
     * @return void
     */
    function addScope($name, $parentScope = self::SCOPE_CONTAINER) {
        $this->container->addScope($name, $parentScope);
    }

    /**
     * Whether this container has the given scope
     *
     * @param string $name
     * @return Boolean
     */
    function hasScope($name) {
        return $this->container->hasScope($name);
    }

    /**
     * Determines whether the given scope is currently active.
     *
     * It does however not check if the scope actually exists.
     *
     * @return Boolean
     */
    function isScopeActive($name) {
        return $this->container->isScopeActive($name);
    }

    /**
     * Gets a parameter.
     *
     * @param  string $name The parameter name
     *
     * @return mixed  The parameter value
     *
     * @throws  \InvalidArgumentException if the parameter is not defined
     */
    public function getParameter($name) {
        return $this->container->getParameter($name);
    }

    /**
     * Returns true if a parameter name is defined.
     *
     * @param  string  $name       The parameter name
     *
     * @return Boolean true if the parameter name is defined, false otherwise
     */
    public function hasParameter($name) {
        return $this->container->hasParameter($name);
    }

    /**
     * Sets a parameter.
     *
     * @param string $name       The parameter name
     * @param mixed  $parameters The parameter value
     */
    public function setParameter($name, $value) {
        $this->container->setParameter($name, $value);
    }

}

